package cn.cnhis.online.service;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.provider.Settings.Secure;
import android.util.Log;

import org.apache.http.conn.ssl.SSLSocketFactory;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttDefaultFilePersistence;
import org.eclipse.paho.client.mqttv3.MqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttPersistenceException;
import org.eclipse.paho.client.mqttv3.MqttTopic;
import org.eclipse.paho.client.mqttv3.internal.MemoryPersistence;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Locale;
import java.util.Properties;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import cn.cnhis.online.BuildConfig;
import cn.cnhis.online.customerView.NotificationHelper;
import cn.cnhis.online.mymodule.Constants;
import cn.cnhis.online.mymodule.sp.MySpUtils;

public class MqttService extends Service implements MqttCallback {

    public static final String DEBUG_TAG = "MqttService"; // Debug TAG

    public static final String PASSWORD_CRET_GUANGZHOUCUISHIZHENJIU = "wSb0stII";   // 广州崔氏针灸 password

    public static final String INTENT_FILTER_CONNECT = "intent_filter_connect";
    public static final String INTENT_FILTER_MQTT = "intent_filter_mqtt";
    public static final String ARG_MQTT_MESSAGE = "arg_mqtt_message";
    public static final String ARG_MQTT_CONNECT = "arg_mqtt_connect";
    public static final int MQTT_QOS_0 = 0; // QOS Level 0 ( Delivery Once no confirmation )

    public static final int MQTT_QOS_1 = 1; // QOS Level 1 ( Delevery at least Once with confirmation )
    public static final int MQTT_QOS_2 = 2; // QOS Level 2 ( Delivery only once with confirmation with handshake )
    private static final String MQTT_THREAD_NAME = "MqttService[" + DEBUG_TAG + "]"; // Handler Thread ID
    private static final String MQTT_BROKER = "mqtt_broker";
    private static final String MQTT_PORT = "mqtt_port";
    private static final String MQTT_TOPIC = "mqtt_topic";
    private static final int MQTT_KEEP_ALIVE = 6000; // KeepAlive Interval in MS
    private static final String MQTT_KEEP_ALIVE_TOPIC_FORAMT = "/users/%s/keepalive"; // Topic format for KeepAlives
    private static final byte[] MQTT_KEEP_ALIVE_MESSAGE = {0}; // Keep Alive message to send
    private static final int MQTT_KEEP_ALIVE_QOS = MQTT_QOS_0; // Default Keepalive QOS

    private static final boolean MQTT_CLEAN_SESSION = true; // Start a clean session?

    private static final String MQTT_URL_FORMAT = "tcp://%s:%s"; // URL Format normally don't change

    private static final String ACTION_START = DEBUG_TAG + ".START"; // Action to start
    private static final String ACTION_STOP = DEBUG_TAG + ".STOP"; // Action to stop
    private static final String ACTION_KEEPALIVE = DEBUG_TAG + ".KEEPALIVE"; // Action to keep alive used by alarm manager
    private static final String ACTION_RECONNECT = DEBUG_TAG + ".RECONNECT"; // Action to reconnect


    private static final String DEVICE_ID_FORMAT = "andr_%s"; // Device ID Format, add any prefix you'd like
    private final BroadcastReceiver mConnectivityReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.i(DEBUG_TAG, "Connectivity Changed...");
        }
    };
    // Note: There is a 23 character limit you will get
    // An NPE if you go over that limit
    private boolean mStarted = false;   // Is the Client started?
    private String mDeviceId;       // Device ID, Secure.ANDROID_ID
    private Handler mConnHandler;     // Seperate Handler thread for networking
    private MqttDefaultFilePersistence mDataStore; // Defaults to FileStore
    private MemoryPersistence mMemStore; // On Fail reverts to MemoryStore
    private MqttConnectOptions mOpts; // Connection Options
    private MqttTopic mKeepAliveTopic; // Instance Variable for Keepalive topic
    private MqttClient mClient; // Mqtt Client
    private AlarmManager mAlarmManager; // Alarm manager to perform repeating tasks
    private ConnectivityManager mConnectivityManager; // To check for connectivity changes
    private Thread mReconnection;
    private String mClientId;
    private String mMqttIp;
    private String mMqttPort;
    private String mMqttTopic;
    private Context mContext;

    public static void actionStart(Context ctx, String ip, String port, String topic) {
        Intent i = new Intent(ctx, MqttService.class);
        i.setAction(ACTION_START);
        i.putExtra(MQTT_BROKER, ip);
        i.putExtra(MQTT_PORT, port);
        i.putExtra(MQTT_TOPIC, topic);
        ctx.startService(i);
    }

    /**
     * Stop MQTT Client
     *
     * @param ctx
     */
    public static void actionStop(Context ctx) {
        Intent i = new Intent(ctx, MqttService.class);
        i.setAction(ACTION_STOP);
        ctx.startService(i);
    }

    public static void actionKeepalive(Context ctx) {
        Intent i = new Intent(ctx, MqttService.class);
        i.setAction(ACTION_KEEPALIVE);
        ctx.startService(i);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Context applicationContext = getApplicationContext();
        JSONObject jsonObject = null;
        try {
            jsonObject = new JSONObject(MySpUtils.getMQINFO(applicationContext));
            if (BuildConfig.DEBUG) Log.d("MqttService", "jsonObject:" + jsonObject);
            MqttService.actionStart(getApplicationContext(),
                    jsonObject.getString(Constants.MQ_SERVER_IP),
                    jsonObject.getString(Constants.MQ_SERVER_PORT),
                    jsonObject.getString(Constants.MQ_SERVER_TOPIC));
        } catch (JSONException e) {
            e.printStackTrace();
        }


    }

    @Override
    public void onCreate() {
        super.onCreate();
        mReconnection = new ReconnectionThread();

        mDeviceId = String.format(DEVICE_ID_FORMAT,
                Secure.getString(getContentResolver(), Secure.ANDROID_ID));

        HandlerThread thread = new HandlerThread(MQTT_THREAD_NAME);
        thread.start();

        mConnHandler = new Handler(thread.getLooper());

        try {
            mDataStore = new MqttDefaultFilePersistence(getCacheDir().getAbsolutePath());
        } catch (MqttPersistenceException e) {
            e.printStackTrace();
            mDataStore = null;
            mMemStore = new MemoryPersistence();
        }

        mOpts = new MqttConnectOptions();
        mOpts.setCleanSession(MQTT_CLEAN_SESSION);
        // TODO: 2019-10-22 加了用户名密码
        mOpts.setUserName("admin");
        mOpts.setPassword("admin".toCharArray());
        mAlarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
        mConnectivityManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);

        String action = intent.getAction();

        Log.i(DEBUG_TAG, "Received action of " + action);

        if (action == null) {
            Log.i(DEBUG_TAG, "Starting service with no action\n Probably from a crash");
        } else {
            if (action.equals(ACTION_START)) {
                Log.i(DEBUG_TAG, "Received ACTION_START");
                mMqttIp = intent.getStringExtra(MQTT_BROKER);
                mMqttPort = intent.getStringExtra(MQTT_PORT);
                mMqttTopic = intent.getStringExtra(MQTT_TOPIC);
                start();
            } else if (action.equals(ACTION_STOP)) {
                stop();
            } else if (action.equals(ACTION_KEEPALIVE)) {
                keepAlive();
            } else if (action.equals(ACTION_RECONNECT)) {
                reconnectIfNecessary();
            }
        }

        return START_REDELIVER_INTENT;
    }

    private synchronized void start() {
        if (mStarted) {
            connectSuccess();
            Log.i(DEBUG_TAG, "Attempt to start while already started");
            return;
        }

        if (hasScheduledKeepAlives()) {
            stopKeepAlives();
        }

        connect();

        registerReceiver(mConnectivityReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
    }

    private synchronized void stop() {
        if (!mStarted) {
            Log.i(DEBUG_TAG, "Attemtpign to stop connection that isn't running");
            return;
        }

        if (mClient != null) {
            mConnHandler.post(() -> {
                try {
                    mClient.disconnect();
                } catch (MqttException ex) {
                    ex.printStackTrace();
                }
                mClient = null;
                mStarted = false;

                stopKeepAlives();
            });
        }

        unregisterReceiver(mConnectivityReceiver);
        stopSelf();
    }

    private synchronized void keepAlive() {
        if (isConnected()) {
            try {
                sendKeepAlive();
                return;
            } catch (MqttConnectivityException ex) {
                ex.printStackTrace();
                reconnectIfNecessary();
            } catch (MqttPersistenceException ex) {
                ex.printStackTrace();
                //     stop();
            } catch (MqttException ex) {
                ex.printStackTrace();
                //     stop();
            }
        }
    }

    private synchronized void reconnectIfNecessary() {
        if (!mReconnection.isAlive()) {
            mReconnection = new ReconnectionThread();
            mReconnection.start();
        }
    }

    private void connectSuccess() {
        Intent intent = new Intent();
        intent.setAction(INTENT_FILTER_CONNECT);
        intent.putExtra(ARG_MQTT_CONNECT, true);
        sendBroadcast(intent);
    }

    private synchronized boolean hasScheduledKeepAlives() {
        Intent i = new Intent();
        i.setClass(this, MqttService.class);
        i.setAction(ACTION_KEEPALIVE);
        PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, PendingIntent.FLAG_NO_CREATE);

        return (pi != null) ? true : false;
    }

    private void stopKeepAlives() {
        Intent i = new Intent();
        i.setClass(this, MqttService.class);
        i.setAction(ACTION_KEEPALIVE);
        PendingIntent pi = PendingIntent.getService(this, 0, i, 0);
        mAlarmManager.cancel(pi);
    }

    private synchronized void connect() {

        initClient();

        mConnHandler.post(() -> {
            try {
                tryConnect();
                connectSuccess();
            } catch (MqttException e) {
                e.printStackTrace();
                connectFailt();
            }
        });
    }

    private boolean isConnected() {
        if (mStarted && mClient != null && !mClient.isConnected()) {
            Log.i(DEBUG_TAG, "Mismatch between what we think is connected and what is connected");
        }
        if (mClient != null) {
            return (mStarted && mClient.isConnected()) ? true : false;
        }
        return false;
    }

    private synchronized MqttDeliveryToken sendKeepAlive()
            throws MqttConnectivityException, MqttPersistenceException, MqttException {
        if (!isConnected())
            throw new MqttConnectivityException();

        if (mKeepAliveTopic == null || mClient.getClientId().equals(mClientId)) {
            mClientId = mClient.getClientId();
            mKeepAliveTopic = mClient.getTopic(
                    String.format(Locale.US, MQTT_KEEP_ALIVE_TOPIC_FORAMT, mDeviceId));
        }

        Log.i(DEBUG_TAG, "Sending Keepalive to " + mMqttIp);

        MqttMessage message = new MqttMessage(MQTT_KEEP_ALIVE_MESSAGE);
        message.setQos(MQTT_KEEP_ALIVE_QOS);

        return mKeepAliveTopic.publish(message);
    }

    private void initClient() {
        String url = String.format(Locale.US, MQTT_URL_FORMAT, mMqttIp, mMqttPort);
        Log.i(DEBUG_TAG, "Connecting with URL: " + url);

        try {
            if (mDataStore != null) {
                Log.i(DEBUG_TAG, "Connecting with DataStore");
                mClient = new MqttClient(url, mDeviceId, mDataStore);
            } else {
                Log.i(DEBUG_TAG, "Connecting with MemStore");
                mClient = new MqttClient(url, mDeviceId, mMemStore);
            }
        } catch (MqttException e) {
            e.printStackTrace();
        }
    }

    private void tryConnect() throws MqttException {
            mClient.connect(mOpts);
            mClient.subscribe(mMqttTopic, 0);
            mClient.setCallback(MqttService.this);
            mStarted = true; // Service is now connected
            Log.i(DEBUG_TAG, "Successfully connected and subscribed starting keep alives");
            startKeepAlives();
    }

    private void connectFailt() {
        Intent intent = new Intent();
        intent.setAction(INTENT_FILTER_CONNECT);
        intent.putExtra(ARG_MQTT_CONNECT, false);
        sendBroadcast(intent);
    }

    private void startKeepAlives() {
        Intent i = new Intent();
        i.setClass(this, MqttService.class);
        i.setAction(ACTION_KEEPALIVE);
        PendingIntent pi = PendingIntent.getService(this, 0, i, 0);
        mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP,
                System.currentTimeMillis() + MQTT_KEEP_ALIVE,
                MQTT_KEEP_ALIVE, pi);
    }

    @Override
    public IBinder onBind(Intent arg0) {
        return null;
    }

    @Override
    public void connectionLost(Throwable arg0) {
        stopKeepAlives();
        mClient = null;
        reconnectIfNecessary();
    }

    @Override
    public void messageArrived(MqttTopic topic, MqttMessage message)
            throws Exception {
        Log.i(DEBUG_TAG, "  Topic:\t" + topic.getName() +
                "  Message:\t" + new String(message.getPayload()) +
                "  QoS:\t" + message.getQos());

        JSONObject jsonObject = new JSONObject(new String(message.getPayload()));

        NotificationHelper.getInstance(getApplicationContext()).show(this, jsonObject.toString());

        Intent intent = new Intent();
        intent.setAction(INTENT_FILTER_MQTT);
        intent.putExtra(ARG_MQTT_MESSAGE, new String(message.getPayload()));
        sendBroadcast(intent);
    }

    @Override
    public void deliveryComplete(MqttDeliveryToken arg0) {

    }

    private class ReconnectionThread extends Thread {

        private int mWaiting;

        @Override
        public void run() {
            super.run();

            while (!isConnected()) {
                try {
                    Thread.sleep((long) waiting() * 1000l);
                    initClient();
                    tryConnect();
                    mWaiting++;
                    Log.d("mqttservice", "执行重连...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (MqttException e) {
                    e.printStackTrace();
                }
            }
        }

        private int waiting() {
            if (mWaiting > 20) {
                return 100;
            }

            if (mWaiting > 13) {
                return 50;
            }

            return mWaiting <= 7 ? 5 : 20;
        }
    }

    private class MqttConnectivityException extends Exception {
        private static final long serialVersionUID = -7385866796799469420L;
    }


    /**
     * SSLSocketFactory
     */
    public class SSLSocketFactoryEx extends SSLSocketFactory {
        SSLContext sslContext = SSLContext.getInstance("TLS");

        public SSLSocketFactoryEx(KeyStore truststore, char[] arry)
                throws NoSuchAlgorithmException, KeyManagementException,
                KeyStoreException, UnrecoverableKeyException {
            super(truststore);
            KeyManagerFactory localKeyManagerFactory =
                    KeyManagerFactory.getInstance(KeyManagerFactory
                            .getDefaultAlgorithm());
            localKeyManagerFactory.init(truststore, arry);
            KeyManager[] arrayOfKeyManager =
                    localKeyManagerFactory.getKeyManagers();
            TrustManager tm = new X509TrustManager() {

                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                }

                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                }

                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
            };

            sslContext.init(arrayOfKeyManager, new TrustManager[]{tm},
                    new java.security.SecureRandom());
        }

        @Override
        public Socket createSocket() throws IOException {
            return sslContext.getSocketFactory().createSocket();
        }

        @Override
        public Socket createSocket(Socket socket, String host, int port,
                                   boolean autoClose) throws IOException, UnknownHostException {
            return sslContext.getSocketFactory().createSocket(socket, host, port,
                    autoClose);
        }
    }
}
